Initial commit master
authorDmitry Shalnoff <dev@shalnoff.com>
Thu, 23 Jun 2016 08:23:16 +0000 (10:23 +0200)
committerDmitry Shalnoff <dev@shalnoff.com>
Thu, 23 Jun 2016 08:23:16 +0000 (10:23 +0200)
.gitignore [new file with mode: 0644]
README.md [new file with mode: 0644]
RGBW_Controller/RGBW_Controller.ino [new file with mode: 0644]
RGBW_Controller/pwm.c [new file with mode: 0644]
make [new file with mode: 0755]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..33bf192
--- /dev/null
@@ -0,0 +1,56 @@
+memo
+info
+
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.bin
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..623129c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,104 @@
+# RGB(W) LED Interplay Medium dendrite module (for ESP8266)
+
+This is IM denrite module (remote wifi network device) created for [Interplay Medium™](https://interplaymedium.org) project.
+
+![Interplay Medium RGB(W) LED Dendrite](https://repository.interplaymedium.org/RGBW%20Controller/IM_RGBW_LED_dendrite.jpeg)
+
+## Pinouts 
+
+For LEDs be sure to add MOSFETs and current limiting resistors appropriately. Wiring scheme will be added probably later. 
+
+![ESP8266 Pinout](https://repository.interplaymedium.org/RGBW%20Controller/esppinout_.png)
+![ESP8266 Programming](https://repository.interplaymedium.org/RGBW%20Controller/usbprogram_.png)
+
+## Preparing the building environment
+
+Make sure that you have the environment installed as described at
+
+1. [makeEspArduino.mk](https://github.com/plerup/makeEspArduino.git)
+2. [esp8266 Arduino SDK](https://github.com/esp8266/Arduino)
+
+3. In the *make* script, change path for each variable approprately: 
+    MAKE_FILE=....
+    ESP_SDK_ROOT=....
+
+## Change your IM AXOD microserver or AP (router) WIFI login and password
+
+create the file 
+    vim ../info
+
+assign SSID and PASSWORD of your local IM AXOD microserver or Access Point in there
+
+    WIFI_SSID="ssid"
+    WIFI_PASS="ssid password"
+
+You can change it later whenewer you want using HTTP interface
+
+## Building 
+
+initial buildong and flashing firmware at once 
+    ./make RGBW_Controller upload
+
+after that you may just build the binary and uload it using remote HTTP interface
+
+    ./make RGBW_Controller  
+    curl -F image=@RGBW_Controller.bin -s http://im_<....>.lan/update
+
+## Usage 
+
+
+By default dendrite can be reached "im_rgb5" doman
+
+Change it with 
+
+    curl "http://im_rgb5?rename=NEWNAME"
+
+Turn on the color and effect 
+
+    curl "im_rgb5?rgbw=aadd00&rotate=100"
+
+Other options
+
+    curl "http://im_rgb5/help"
+
+## Todo 
+
+This firmware is in progress. Here is a brief list of upcoming changes. 
+
+* make default URL as IM_(LAST 4 DIGITS OF MAC ADDRESS)
+* add interface features (save, reset....)
+* state return in 2 variants  
+    txt (default)  
+    JSON  
+* add commands  
+    switch (like rgbw=00ff0000) for latch switchers  
+    dimm (for dimmer, HW prototype required, 220v)  
+    rgbwdef -- save default values in EEPROM, which is returning on reset command  
+    rotatedef -- same for rotation  
+  
+* switching AP/slave, AP by defuault
+    remote access setup (host name, AP/slave, SSID, passw)  
+    save in EEPROM  
+
+## License
+
+Copyright © 2016 Dmitry Shalnov [interplaymedium.org]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this files except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+
+
+
+
diff --git a/RGBW_Controller/RGBW_Controller.ino b/RGBW_Controller/RGBW_Controller.ino
new file mode 100644 (file)
index 0000000..b1bb372
--- /dev/null
@@ -0,0 +1,482 @@
+/*
+/*
+ * RGB(W) LED IM Dendrite module (for ESP8266)
+ * Created for Interplaymedium™ project (https://interplaymedium.org)
+ * Copyright © 2016 Dmitry Shalnov [interplaymedium.org]
+ * Licensed under the Apache License, Version 2.0
+*/
+
+#include "../../info"
+
+#include <ESP8266WiFi.h>
+#include <WiFiClient.h>
+#include <ESP8266WebServer.h>
+#include <ESP8266mDNS.h>
+#include <EEPROM.h>
+
+#define        VERSION         "0.1.4"
+
+#define EEPROM_STR_MAX_LEN     16
+#define EEPROM_OTHER_MAX_LEN   8
+
+#define EEPROM_STR             0
+#define EEPROM_STR_FLAG                17      // watch overlap: EEPROM_STR_MAX_LEN
+#define EEPROM_R               18
+#define EEPROM_G               19
+#define EEPROM_B               20
+#define EEPROM_W               21
+#define EEPROM_ROT_H           22
+#define EEPROM_ROT_L           23
+
+// -------------------- PWM settings  ----------------------------------------------------
+
+extern "C"{
+       #include "pwm.h"        // Includes of Expressif SDK
+}
+
+#define        LED1            0
+#define        LED2            2
+#define        LED3            3
+#define        LED4            1
+
+#define PWM_CHANNELS   4
+
+#define PWMSTEPS       255
+#define PWM_PERIOD     5000 // Period of PWM frequency, SDK default: 5000 -> * 200ns ^= 1 kHz
+
+unsigned int CIEL8[ PWMSTEPS ];
+
+int R=0, G=0, B=0, W=0;
+unsigned int Rnew=0, Gnew=0, Bnew=0, Wnew=0;
+unsigned int rotateDelay = 0;
+int signR = 1, signG = 1, signB = 1, signW = 1;
+unsigned int eTimer = 0;
+
+uint32 pwm_duty_init[PWM_CHANNELS]; // PWM initial duty: OFF by default
+
+uint32 io_info[PWM_CHANNELS][3] = {
+
+//     MUX,             FUNC,          PIN
+
+//     {PERIPHS_IO_MUX_GPIO5_U, FUNC_GPIO5,     5}, // D1
+//     {PERIPHS_IO_MUX_GPIO4_U, FUNC_GPIO4,     4}, // D2
+       {PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0,     LED1}, // D3   0
+       {PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2,     LED2}, // D4   2
+
+       {PERIPHS_IO_MUX_U0RXD_U, FUNC_GPIO3,     LED3}, //      RX      3
+       {PERIPHS_IO_MUX_U0TXD_U, FUNC_GPIO1,     LED4}, //      TX      1
+
+//     {PERIPHS_IO_MUX_MTMS_U, FUNC_GPIO14, 14}, // D5
+//     {PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12, 12}, // D6
+//     {PERIPHS_IO_MUX_MTCK_U, FUNC_GPIO13, 13}, // D7
+//     {PERIPHS_IO_MUX_MTDO_U, FUNC_GPIO15 ,15}, // D8
+
+//     D0 - not have PWM :-(                16
+};
+
+// ------------------- server settings --------------------------------------------------
+
+#define HOST_DEFAULT                   "im_rgb5"
+
+char host[ EEPROM_STR_MAX_LEN ];
+
+const char* ssid       = IM_WIFI_SSID;
+const char* password   = IM_WIFI_PASS;
+
+ESP8266WebServer       server(80);
+
+String                 HTTPresp;
+
+// ----------------- change current R G B (W) to another RGBW set ----------------------
+
+void changeRGBto( int newR, int newG, int newB, int newW ){
+
+       float stepR, stepG, stepB, stepW;
+       int R2, G2, B2, W2;
+/*
+//     int R, G, B, W;
+
+//     R = EEPROM.read( 0 );
+//     G = EEPROM.read( 1 );
+//     B = EEPROM.read( 2 );
+//     W = EEPROM.read( 3 );
+*/
+       stepR = (newR - R) / (float)PWMSTEPS;
+       stepG = (newG - G) / (float)PWMSTEPS;
+       stepB = (newB - B) / (float)PWMSTEPS;
+       stepW = (newW - W) / (float)PWMSTEPS;
+
+       for ( unsigned int a = 0; a < PWMSTEPS; a++ ){
+
+               R2 = R + round( stepR * (float)a );
+               G2 = G + round( stepG * (float)a );
+               B2 = B + round( stepB * (float)a );
+               W2 = W + round( stepW * (float)a );
+
+               pwm_set_duty( CIEL8[R2], 0 );
+               pwm_set_duty( CIEL8[G2], 1 );
+               pwm_set_duty( CIEL8[B2], 2 );
+               pwm_set_duty( CIEL8[W2], 3 );
+
+               pwm_start(); // commit
+
+               delay( 500 / PWMSTEPS );
+       }
+}
+
+// ----------- explode for selected substring -----------
+
+String expld( String str, unsigned int numb, char delimiter ){
+       
+       unsigned int cnt = 0, a = 0, p2 = 0, p1 = 0, lng = 0;
+
+       lng = str.length();
+
+       for ( a = 0; a < lng; a++ ){
+               if ( str.charAt( a ) == delimiter ) { 
+                       p2 = p1;
+                       p1 = a;
+                       if ( cnt == numb ) break;
+                       cnt ++;
+               }
+       }
+
+       if ( a == lng ) { 
+               p2 = p1;
+               p1 = lng;
+       }
+
+       if ( numb > 0 ) p2 ++;
+
+       return str.substring(p2, p1);
+       
+}
+
+unsigned char URIHasArg( String str, String arg ){
+
+
+       unsigned char a = 0, delimiterCnt = 0, lng = str.length();
+
+       for ( a = 0; a < lng; a++ ){
+               if ( str.charAt( a ) == '/' ) delimiterCnt++;
+       }
+
+       for ( a=0; a < 10; a++ ){
+               if ( arg.equals( expld( str, a, '/' ) ) ) return a;     
+       }
+       return 0;
+}
+
+// ---------------- EEPROM String r/w -------------------
+
+void EEPROMStrRead( unsigned char addr, char * str ){
+       for (unsigned char a = addr; a <  EEPROM_STR_MAX_LEN; a++ ){
+               str[ a ] = EEPROM.read( a );
+               if (str[ a ] == 0) break;
+       }
+}
+
+void EEPROMStrWrite( unsigned char addr, char * str ){
+       unsigned char a = 0;
+       for (a = addr; a <  EEPROM_STR_MAX_LEN; a++ ){
+               EEPROM.write( a, str[ a ] );
+               if (str[ a ] == 0) break;
+       }
+       EEPROM.write( a, 0 );
+}
+
+// ---------------- misc ---------------------------------
+
+int str2HEX(const String str) {    
+    return strtol( str.c_str(), 0, 16 );
+}
+
+void blink( unsigned char times ){
+       for (unsigned char a = 0; a < times; a++ ){
+               digitalWrite(LED4, HIGH);
+               delay (50);
+               digitalWrite(LED4, LOW);
+               delay (50);
+       }
+}
+
+// --------------- Init --------------------------------
+
+void setup(void) {
+
+       HTTPresp.reserve(800); // lenght of Help message generally
+
+       // calculate lookup array 
+       for (unsigned int a = 1; a < PWMSTEPS; a++) CIEL8[ a ] = round( pow( PWM_PERIOD, (double)a / (PWMSTEPS-1) ) ); // calculate exponential lookup array for PWM
+       CIEL8[ 0 ] = 0;
+
+       // init LED pins
+
+       pinMode(LED1, OUTPUT);
+       pinMode(LED2, OUTPUT);
+       pinMode(LED3, OUTPUT);
+       pinMode(LED4, OUTPUT);
+
+       digitalWrite(LED1, LOW);
+       digitalWrite(LED2, LOW);
+       digitalWrite(LED3, LOW);
+       digitalWrite(LED4, LOW);
+
+       // PWM inti
+
+       for (uint8_t channel = 0; channel < PWM_CHANNELS; channel++) pwm_duty_init[channel] = 0;        // Initial duty -> all off
+       uint32_t period = PWM_PERIOD;
+       pwm_init(period, pwm_duty_init, PWM_CHANNELS, io_info);
+       pwm_start();
+
+       // Serial init
+#if SERIAL == 1
+       Serial.begin(115200);
+       Serial.println();
+       Serial.println("Booting Sketch...0");
+#endif
+
+       // mDNS init
+
+//     deviceURI.reserve(7);
+//     deviceURI = "im" + WiFi.macAddress().substring(12, 14) + WiFi.macAddress().substring(15, 17); // 00:00:00:00:00:00
+
+       EEPROM.begin( EEPROM_STR_MAX_LEN + EEPROM_OTHER_MAX_LEN );
+       
+       if ( EEPROM.read( EEPROM_STR_FLAG ) == 1 ){
+               EEPROMStrRead( EEPROM_STR, host ); 
+       } else {
+               strcpy( host, HOST_DEFAULT ); // default URI and host name
+       }
+
+       WiFi.hostname( host );
+
+//     WiFi.softAP(APssid, APpassword);
+
+//     WiFi.mode(WIFI_AP);
+//     WiFi.mode(WIFI_AP_STA); 
+       WiFi.mode(WIFI_STA);
+       WiFi.begin(ssid, password);
+
+       if (WiFi.waitForConnectResult() == WL_CONNECTED) {
+
+//             MDNS.begin( deviceURI.c_str() );
+               MDNS.begin( host );
+
+               // default 
+
+               server.onNotFound( []() {
+                       server.sendHeader("Connection", "close");
+
+                       unsigned int RGBW = 0xff;
+                       String param = "";
+                       String command = "";
+
+                       // parse plain URI arguments TODO: not sure whether we need it, check Plan 9 specifications
+/*
+                       param = server.arg("rgbw");
+                       if ( param == "" ) {
+                               param = URIHasArg( server.uri(), "rgbw" );
+                       }
+*/
+                       // color change 
+
+                       if ( server.hasArg("rgbw") ){ // || param != 0
+                               
+                               param = server.arg("rgbw");
+
+                               Rnew = str2HEX( param.substring(0, 2) );
+                               Gnew = str2HEX( param.substring(2, 4) );
+                               Bnew = str2HEX( param.substring(4, 6) );
+                               Wnew = str2HEX( param.substring(6, 8) );
+
+                               changeRGBto( Rnew, Gnew, Bnew, Wnew );
+
+                               R = Rnew;
+                               G = Gnew;
+                               B = Bnew;
+                               W = Wnew;
+                       }
+
+                       // rotate
+
+                       if ( server.hasArg("rotate") ){
+                               rotateDelay = str2HEX( server.arg("rotate") );
+                       }
+
+                       // rename host 
+
+                       if ( server.hasArg("rename") ) {
+                               EEPROMStrWrite( EEPROM_STR, (char *)server.arg("rename").c_str() );
+                               EEPROM.write( EEPROM_STR_FLAG, 1 );
+                               EEPROM.commit();
+
+                               server.sendHeader("Connection", "close");
+                               server.send_P(200, "text/plain", PSTR("Done. System rebooting...\n") );
+
+                               delay(500);
+                               ESP.restart();
+                       }
+
+                       // ------
+
+                       HTTPresp = "host: " + String(host) + ".lan" + "\n";
+                       HTTPresp += "URI: " + server.uri() + "\n";
+//                     HTTPresp += "Arg RGBW: " + String( URIHasArg( server.uri(), "test2" ) ) + "\n";         // URI contains /test2/
+//                     HTTPresp += "Arg RGBW: " + server.arg("aaaa") + "\n";                                   // GET or POST params has "aaaa"
+                       HTTPresp += "rotate: " + String(rotateDelay) + "\n";
+                       HTTPresp += "rgbw: " + String(R) + " " + String(G) + " " + String(B) + " " + String(W) + "\n";
+               
+                       server.send(200, "text/plain", HTTPresp);
+               });
+
+               // test EEPROM string
+
+               server.on("/eeprom", HTTP_GET, []() {
+
+                       char * testStr2 = "asdfghjkl12345";
+
+                       server.setContentLength(CONTENT_LENGTH_UNKNOWN);
+                       server.send(200, "text/plain", "");
+
+                       EEPROMStrRead( EEPROM_STR, testStr2 );
+
+                       server.sendContent( testStr2 );
+                       server.sendContent( "\n" );
+               });
+
+               // help
+
+               server.on("/help", HTTP_GET, []() {
+
+                       HTTPresp =      "Interplay Medium ESP8266 LED RGB(W) PWM Controller. Version: " + String(VERSION) + "\n";
+                       HTTPresp +=     "Written by Dmitry Shalnov (c) 2017. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. \n\n";
+
+                       HTTPresp +=     " rgbw          Red Green Blue and White components in hexadecimal form (e.g. ff00ff00)\n";
+                       HTTPresp +=     " rotate        Delay of components rotation FX in hexadecimal (e.g. ffff), set 0 to stop\n";
+                       HTTPresp +=     " update        Wireless update of firmware (see example below)\n";
+                       HTTPresp +=     " help          Send this help\n\n";
+
+                       HTTPresp +=     "Usage:         curl http://" + String(host) + ".lan [--data \"rgbw=<hex RGBW>\"] [--data \"rotate=<hex delay, 0 = stop >\"] \n";
+                       HTTPresp +=     "               curl http://" + String(host) + ".lan[/rgbw/<hex RGBW>][/rotate/<hex delay>] \n";
+                       HTTPresp +=     "Examples:      curl http://" + String(host) + ".lan/?rgbw=00ff00ff&rotate=ff \n";
+                       HTTPresp +=     "               curl -F image=@RGBW_Controller.bin " + String(host) + ".lan/update \n";
+
+                       server.sendHeader("Connection", "close");
+                       server.send( 200, "text/plain", HTTPresp );
+
+               });
+
+               // OTA update
+
+               server.on("/update", HTTP_POST, []() {
+
+                       server.sendHeader("Connection", "close");
+                       server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
+
+                       ESP.restart();
+
+                       }, []() {
+
+                               HTTPUpload& upload = server.upload();
+
+                               if (upload.status == UPLOAD_FILE_START) {
+
+                                       rotateDelay = 0;
+                                       blink( 3 );
+                                       
+#if SERIAL == 1
+                                       Serial.setDebugOutput(true);
+#endif
+                                       WiFiUDP::stopAll();
+
+#if SERIAL == 1
+                                       Serial.printf("Update: %s\n", upload.filename.c_str());
+#endif
+                                       uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
+
+                                       if (!Update.begin(maxSketchSpace)) { //start with max available size
+#if SERIAL == 1
+                                               Update.printError(Serial);
+#endif
+                                       }
+                               } else if (upload.status == UPLOAD_FILE_WRITE) {
+                                       if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
+#if SERIAL == 1
+                                               Update.printError(Serial);
+#endif
+                                       }
+                               } else if (upload.status == UPLOAD_FILE_END) {
+                                       if (Update.end(true)) { //true to set the size to the current progress
+
+                                               server.sendHeader("Connection", "close");
+                                               server.send_P(200, "text/plain", PSTR("Success. Please wait until device replace firmware and boot up...\n") );
+#if SERIAL == 1
+                                               Serial.printf("Update Success: %u\nRebooting....\n", upload.totalSize); 
+#endif
+                                       } else {
+                                               server.sendHeader("Connection", "close");
+                                               server.send_P(200, "text/plain", PSTR("Something went wrong. Please reset device and try again.\n") );
+#if SERIAL == 1
+                                               Update.printError(Serial);
+#endif
+                                       }
+#if SERIAL == 1
+                                       Serial.setDebugOutput(false);
+#endif
+                               }
+
+                               yield();
+               });
+
+               // start server 
+
+               server.begin();
+               MDNS.addService("http", "tcp", 80);
+#if SERIAL == 1
+               Serial.printf("Ready! Open http://%s.local in your browser\n", host);
+#endif
+
+       } else {
+#if SERIAL == 1
+               Serial.println("WiFi Failed");
+#endif
+       }
+}
+
+void loop(void) {
+
+       server.handleClient();
+       MDNS.update();
+
+       eTimer ++;
+
+       if ( eTimer >= rotateDelay && rotateDelay != 0 ) {
+
+               eTimer = 0;
+
+               R = R + signR;
+               G = G + signG;  
+               B = B + signB;
+               W = W + signW;
+
+               if ( R >= PWMSTEPS-1 ) { signR = -1; R = PWMSTEPS-1; }
+               if ( G >= PWMSTEPS-1 ) { signG = -1; G = PWMSTEPS-1; }
+               if ( B >= PWMSTEPS-1 ) { signB = -1; B = PWMSTEPS-1; }
+               if ( W >= PWMSTEPS-1 ) { signW = -1; W = PWMSTEPS-1; }
+
+               if ( R < 2 ) { signR = 1; R = 2; }
+               if ( G < 2 ) { signG = 1; G = 2; }
+               if ( B < 2 ) { signB = 1; B = 2; }
+               if ( W < 2 ) { signW = 1; W = 2; }
+
+               pwm_set_duty( CIEL8[R], 0 );
+               pwm_set_duty( CIEL8[G], 1 );
+               pwm_set_duty( CIEL8[B], 2 );
+               pwm_set_duty( CIEL8[W], 3 );
+
+               pwm_start(); // commit
+
+       }
+}
diff --git a/RGBW_Controller/pwm.c b/RGBW_Controller/pwm.c
new file mode 100644 (file)
index 0000000..6df21ac
--- /dev/null
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2016 Stefan Brüns <stefan.bruens@rwth-aachen.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+/* Set the following three defines to your needs */
+
+#ifndef SDK_PWM_PERIOD_COMPAT_MODE
+  #define SDK_PWM_PERIOD_COMPAT_MODE 0
+#endif
+#ifndef PWM_MAX_CHANNELS
+  #define PWM_MAX_CHANNELS 8
+#endif
+#define PWM_DEBUG 0
+#define PWM_USE_NMI 0
+
+/* no user servicable parts beyond this point */
+
+#define PWM_MAX_TICKS 0x7fffff
+#if SDK_PWM_PERIOD_COMPAT_MODE
+#define PWM_PERIOD_TO_TICKS(x) (x * 0.2)
+#define PWM_DUTY_TO_TICKS(x) (x * 5)
+#define PWM_MAX_DUTY (PWM_MAX_TICKS * 0.2)
+#define PWM_MAX_PERIOD (PWM_MAX_TICKS * 5)
+#else
+#define PWM_PERIOD_TO_TICKS(x) (x)
+#define PWM_DUTY_TO_TICKS(x) (x)
+#define PWM_MAX_DUTY PWM_MAX_TICKS
+#define PWM_MAX_PERIOD PWM_MAX_TICKS
+#endif
+
+#include <c_types.h>
+#include <pwm.h>
+#include <eagle_soc.h>
+#include <ets_sys.h>
+
+// from SDK hw_timer.c
+#define TIMER1_DIVIDE_BY_16             0x0004
+#define TIMER1_ENABLE_TIMER             0x0080
+
+struct pwm_phase {
+       uint32_t ticks;    ///< delay until next phase, in 200ns units
+       uint16_t on_mask;  ///< GPIO mask to switch on
+       uint16_t off_mask; ///< GPIO mask to switch off
+};
+
+/* Three sets of PWM phases, the active one, the one used
+ * starting with the next cycle, and the one updated
+ * by pwm_start. After the update pwm_next_set
+ * is set to the last updated set. pwm_current_set is set to
+ * pwm_next_set from the interrupt routine during the first
+ * pwm phase
+ */
+typedef struct pwm_phase (pwm_phase_array)[PWM_MAX_CHANNELS + 2];
+static pwm_phase_array pwm_phases[3];
+static struct {
+       struct pwm_phase* next_set;
+       struct pwm_phase* current_set;
+       uint8_t current_phase;
+} pwm_state;
+
+static uint32_t pwm_period;
+static uint32_t pwm_period_ticks;
+static uint32_t pwm_duty[PWM_MAX_CHANNELS];
+static uint16_t gpio_mask[PWM_MAX_CHANNELS];
+static uint8_t pwm_channels;
+
+// 3-tuples of MUX_REGISTER, MUX_VALUE and GPIO number
+typedef uint32_t (pin_info_type)[3];
+
+struct gpio_regs {
+       uint32_t out;         /* 0x60000300 */
+       uint32_t out_w1ts;    /* 0x60000304 */
+       uint32_t out_w1tc;    /* 0x60000308 */
+       uint32_t enable;      /* 0x6000030C */
+       uint32_t enable_w1ts; /* 0x60000310 */
+       uint32_t enable_w1tc; /* 0x60000314 */
+       uint32_t in;          /* 0x60000318 */
+       uint32_t status;      /* 0x6000031C */
+       uint32_t status_w1ts; /* 0x60000320 */
+       uint32_t status_w1tc; /* 0x60000324 */
+};
+static struct gpio_regs* gpio = (struct gpio_regs*)(0x60000300);
+
+struct timer_regs {
+       uint32_t frc1_load;   /* 0x60000600 */
+       uint32_t frc1_count;  /* 0x60000604 */
+       uint32_t frc1_ctrl;   /* 0x60000608 */
+       uint32_t frc1_int;    /* 0x6000060C */
+       uint8_t  pad[16];
+       uint32_t frc2_load;   /* 0x60000620 */
+       uint32_t frc2_count;  /* 0x60000624 */
+       uint32_t frc2_ctrl;   /* 0x60000628 */
+       uint32_t frc2_int;    /* 0x6000062C */
+       uint32_t frc2_alarm;  /* 0x60000630 */
+};
+static struct timer_regs* timer = (struct timer_regs*)(0x60000600);
+
+static void ICACHE_RAM_ATTR
+pwm_intr_handler(void)
+{
+       if ((pwm_state.current_set[pwm_state.current_phase].off_mask == 0) &&
+           (pwm_state.current_set[pwm_state.current_phase].on_mask == 0)) {
+               pwm_state.current_set = pwm_state.next_set;
+               pwm_state.current_phase = 0;
+       }
+
+       do {
+               // force write to GPIO registers on each loop
+               asm volatile ("" : : : "memory");
+
+               gpio->out_w1ts = (uint32_t)(pwm_state.current_set[pwm_state.current_phase].on_mask);
+               gpio->out_w1tc = (uint32_t)(pwm_state.current_set[pwm_state.current_phase].off_mask);
+
+               uint32_t ticks = pwm_state.current_set[pwm_state.current_phase].ticks;
+
+               pwm_state.current_phase++;
+
+               if (ticks) {
+                       if (ticks >= 16) {
+                               // constant interrupt overhead
+                               ticks -= 9;
+                               timer->frc1_int &= ~FRC1_INT_CLR_MASK;
+                               WRITE_PERI_REG(&timer->frc1_load, ticks);
+                               return;
+                       }
+
+                       ticks *= 4;
+                       do {
+                               ticks -= 1;
+                               // stop compiler from optimizing delay loop to noop
+                               asm volatile ("" : : : "memory");
+                       } while (ticks > 0);
+               }
+
+       } while (1);
+}
+
+/**
+ * period: initial period (base unit 1us OR 200ns)
+ * duty: array of initial duty values, may be NULL, may be freed after pwm_init
+ * pwm_channel_num: number of channels to use
+ * pin_info_list: array of pin_info
+ */
+void ICACHE_FLASH_ATTR
+pwm_init(uint32_t period, uint32_t *duty, uint32_t pwm_channel_num,
+              uint32_t (*pin_info_list)[3])
+{
+       int i, j, n;
+
+       pwm_channels = pwm_channel_num;
+       if (pwm_channels > PWM_MAX_CHANNELS)
+               pwm_channels = PWM_MAX_CHANNELS;
+
+       for (i = 0; i < 3; i++) {
+               for (j = 0; j < (PWM_MAX_CHANNELS + 2); j++) {
+                       pwm_phases[i][j].ticks = 0;
+                       pwm_phases[i][j].on_mask = 0;
+                       pwm_phases[i][j].off_mask = 0;
+               }
+       }
+       pwm_state.current_set = pwm_state.next_set = 0;
+       pwm_state.current_phase = 0;
+
+       uint32_t all = 0;
+       // PIN info: MUX-Register, Mux-Setting, PIN-Nr
+       for (n = 0; n < pwm_channels; n++) {
+               pin_info_type* pin_info = &pin_info_list[n];
+               PIN_FUNC_SELECT((*pin_info)[0], (*pin_info)[1]);
+               gpio_mask[n] = 1 << (*pin_info)[2];
+               all |= 1 << (*pin_info)[2];
+               if (duty)
+                       pwm_set_duty(duty[n], n);
+       }
+       GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, all);
+       GPIO_REG_WRITE(GPIO_ENABLE_W1TS_ADDRESS, all);
+
+       pwm_set_period(period);
+
+#if PWM_USE_NMI
+       ETS_FRC_TIMER1_NMI_INTR_ATTACH(pwm_intr_handler);
+#else
+       ETS_FRC_TIMER1_INTR_ATTACH(pwm_intr_handler, NULL);
+#endif
+       TM1_EDGE_INT_ENABLE();
+
+       timer->frc1_int &= ~FRC1_INT_CLR_MASK;
+       timer->frc1_ctrl = 0;
+
+       pwm_start();
+}
+
+__attribute__ ((noinline))
+static uint8_t ICACHE_FLASH_ATTR
+_pwm_phases_prep(struct pwm_phase* pwm)
+{
+       uint8_t n, phases;
+
+       uint16_t off_mask = 0;
+       for (n = 0; n < pwm_channels + 2; n++) {
+               pwm[n].ticks = 0;
+               pwm[n].on_mask = 0;
+               pwm[n].off_mask = 0;
+       }
+       phases = 1;
+       for (n = 0; n < pwm_channels; n++) {
+               uint32_t ticks = PWM_DUTY_TO_TICKS(pwm_duty[n]);
+               if (ticks == 0) {
+                       pwm[0].off_mask |= gpio_mask[n];
+               } else if (ticks >= pwm_period_ticks) {
+                       pwm[0].on_mask |= gpio_mask[n];
+               } else {
+                       if (ticks < (pwm_period_ticks/2)) {
+                               pwm[phases].ticks = ticks;
+                               pwm[0].on_mask |= gpio_mask[n];
+                               pwm[phases].off_mask = gpio_mask[n];
+                       } else {
+                               pwm[phases].ticks = pwm_period_ticks - ticks;
+                               pwm[phases].on_mask = gpio_mask[n];
+                               pwm[0].off_mask |= gpio_mask[n];
+                       }
+                       phases++;
+               }
+       }
+       pwm[phases].ticks = pwm_period_ticks;
+
+       // bubble sort, lowest to hightest duty
+       n = 2;
+       while (n < phases) {
+               if (pwm[n].ticks < pwm[n - 1].ticks) {
+                       struct pwm_phase t = pwm[n];
+                       pwm[n] = pwm[n - 1];
+                       pwm[n - 1] = t;
+                       if (n > 2)
+                               n--;
+               } else {
+                       n++;
+               }
+       }
+
+#if PWM_DEBUG
+        int t = 0;
+       for (t = 0; t <= phases; t++) {
+               ets_printf("%d @%d:   %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask);
+       }
+#endif
+
+       // shift left to align right edge;
+       uint8_t l = 0, r = 1;
+       while (r <= phases) {
+               uint32_t diff = pwm[r].ticks - pwm[l].ticks;
+               if (diff && (diff <= 16)) {
+                       uint16_t mask = pwm[r].on_mask | pwm[r].off_mask;
+                       pwm[l].off_mask ^= pwm[r].off_mask;
+                       pwm[l].on_mask ^= pwm[r].on_mask;
+                       pwm[0].off_mask ^= pwm[r].on_mask;
+                       pwm[0].on_mask ^= pwm[r].off_mask;
+                       pwm[r].ticks = pwm_period_ticks - diff;
+                       pwm[r].on_mask ^= mask;
+                       pwm[r].off_mask ^= mask;
+               } else {
+                       l = r;
+               }
+               r++;
+       }
+
+#if PWM_DEBUG
+       for (t = 0; t <= phases; t++) {
+               ets_printf("%d @%d:   %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask);
+       }
+#endif
+
+       // sort again
+       n = 2;
+       while (n <= phases) {
+               if (pwm[n].ticks < pwm[n - 1].ticks) {
+                       struct pwm_phase t = pwm[n];
+                       pwm[n] = pwm[n - 1];
+                       pwm[n - 1] = t;
+                       if (n > 2)
+                               n--;
+               } else {
+                       n++;
+               }
+       }
+
+       // merge same duty
+       l = 0, r = 1;
+       while (r <= phases) {
+               if (pwm[r].ticks == pwm[l].ticks) {
+                       pwm[l].off_mask |= pwm[r].off_mask;
+                       pwm[l].on_mask |= pwm[r].on_mask;
+                       pwm[r].on_mask = 0;
+                       pwm[r].off_mask = 0;
+               } else {
+                       l++;
+                       if (l != r) {
+                               struct pwm_phase t = pwm[l];
+                               pwm[l] = pwm[r];
+                               pwm[r] = t;
+                       }
+               }
+               r++;
+       }
+       phases = l;
+
+#if PWM_DEBUG
+       for (t = 0; t <= phases; t++) {
+               ets_printf("%d @%d:   %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask);
+       }
+#endif
+
+       // transform absolute end time to phase durations
+       for (n = 0; n < phases; n++) {
+               pwm[n].ticks =
+                       pwm[n + 1].ticks - pwm[n].ticks;
+               // subtract common overhead
+               pwm[n].ticks--;
+       }
+       pwm[phases].ticks = 0;
+
+       // do a cyclic shift if last phase is short
+       if (pwm[phases - 1].ticks < 16) {
+               for (n = 0; n < phases - 1; n++) {
+                       struct pwm_phase t = pwm[n];
+                       pwm[n] = pwm[n + 1];
+                       pwm[n + 1] = t;
+               }
+       }
+
+#if PWM_DEBUG
+       for (t = 0; t <= phases; t++) {
+               ets_printf("%d +%d:   %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask);
+       }
+       ets_printf("\n");
+#endif
+
+       return phases;
+}
+
+void ICACHE_FLASH_ATTR
+pwm_start(void)
+{
+       pwm_phase_array* pwm = &pwm_phases[0];
+
+       if ((*pwm == pwm_state.next_set) ||
+           (*pwm == pwm_state.current_set))
+               pwm++;
+       if ((*pwm == pwm_state.next_set) ||
+           (*pwm == pwm_state.current_set))
+               pwm++;
+
+       uint8_t phases = _pwm_phases_prep(*pwm);
+
+        // all with 0% / 100% duty - stop timer
+       if (phases == 1) {
+               if (pwm_state.next_set) {
+#if PWM_DEBUG
+                       ets_printf("PWM stop\n");
+#endif
+                       timer->frc1_ctrl = 0;
+                       ETS_FRC1_INTR_DISABLE();
+               }
+               pwm_state.next_set = NULL;
+
+               GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, (*pwm)[0].on_mask);
+               GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, (*pwm)[0].off_mask);
+
+               return;
+       }
+
+       // start if not running
+       if (!pwm_state.next_set) {
+#if PWM_DEBUG
+               ets_printf("PWM start\n");
+#endif
+               pwm_state.current_set = pwm_state.next_set = *pwm;
+               pwm_state.current_phase = phases - 1;
+               ETS_FRC1_INTR_ENABLE();
+               RTC_REG_WRITE(FRC1_LOAD_ADDRESS, 0);
+               timer->frc1_ctrl = TIMER1_DIVIDE_BY_16 | TIMER1_ENABLE_TIMER;
+               return;
+       }
+
+       pwm_state.next_set = *pwm;
+}
+
+void ICACHE_FLASH_ATTR
+pwm_set_duty(uint32_t duty, uint8_t channel)
+{
+       if (channel > PWM_MAX_CHANNELS)
+               return;
+
+       if (duty > PWM_MAX_DUTY)
+               duty = PWM_MAX_DUTY;
+
+       pwm_duty[channel] = duty;
+}
+
+uint32_t ICACHE_FLASH_ATTR
+pwm_get_duty(uint8_t channel)
+{
+       if (channel > PWM_MAX_CHANNELS)
+               return 0;
+       return pwm_duty[channel];
+}
+
+void ICACHE_FLASH_ATTR
+pwm_set_period(uint32_t period)
+{
+       pwm_period = period;
+
+       if (pwm_period > PWM_MAX_PERIOD)
+               pwm_period = PWM_MAX_PERIOD;
+
+       pwm_period_ticks = PWM_PERIOD_TO_TICKS(period);
+}
+
+uint32_t ICACHE_FLASH_ATTR
+pwm_get_period(void)
+{
+       return pwm_period;
+}
+
+uint32_t ICACHE_FLASH_ATTR
+get_pwm_version(void)
+{
+       return 1;
+}
+
+void ICACHE_FLASH_ATTR
+set_pwm_debug_en(uint8_t print_en)
+{
+       (void) print_en;
+}
+
diff --git a/make b/make
new file mode 100755 (executable)
index 0000000..7f1c38c
--- /dev/null
+++ b/make
@@ -0,0 +1,58 @@
+#!/bin/bash 
+
+if [ $# -lt 1 ]; then
+       echo "Usage: $0 <sketch name> [upload]"
+       exit 0
+fi 
+
+# assign WIFI_SSID and WIFI_PASS in external file ../info
+# source "../info"
+
+SKETCH="$1/$1.ino"
+
+TMP="/tmp/ESPcompile.tmp"
+
+MAKE_FILE="~/Bin/SDK/ESP/makeEspArduino/makeEspArduino.mk"
+ESP_SDK_ROOT=~/Bin/SDK/arduino-1.8.5/hardware/esp8266com/esp8266       # keep it without quotation marks
+
+# nodemcuv2, generic, esp8285
+# F_CPU=80000000L 
+# BUILD_EXTRA_FLAGS="-DIM_WIFI_SSID=\"$WIFI_SSID\" -DIM_WIFI_PASS=\"$WIFI_PASS\""
+# make clean -f "$MAKE_FILE" ESP_ROOT=$ESP_SDK_ROOT F_CPU=80000000L CHIP=esp8266 BOARD=esp8285 SKETCH="$SKETCH" $2
+stdbuf -oL make -f "$MAKE_FILE" ESP_ROOT=$ESP_SDK_ROOT F_CPU=80000000L CHIP=esp8266 BOARD=esp8285 SKETCH="$SKETCH" $2 2>&1 | tee "$TMP"
+
+if [ -s "$TMP" ]; then 
+       binSRC=$( cat "$TMP" | grep Linking | sed -e 's/Linking //g' )
+       cp "$binSRC" .
+
+       serialPort=$(cat "$TMP" | grep "opening port" | awk '{split($0,a," "); print a[3]}')
+else
+       serialPort=/dev/ttyUSB0
+fi
+
+echo $serialPort
+
+exit 0 
+
+
+
+
+sleep 1
+
+if cat "$TMP" | grep -q 'error'; then 
+       echo "exit"
+else 
+       if [ "$1" != "noserial" ]; then
+
+               echo "Connecting $serialPort"
+
+               while [ 1 ]; do
+                       cat $serialPort
+                       sleep 1
+               done
+       else 
+               echo "Serial terminal omited"
+       fi
+fi 
+
+rm "$TMP"
Contact me: dev (at) shalnoff (dot) com
PGP fingerprint: A6B8 3B23 6013 F18A 0C71 198B 83D8 C64D 917A 5717